/****************************************************
          File Name:request_action.c
          Author: Abby Cin
          Mail: abbytsing@gmail.com
          Created Time: Tue 13 Jan 2015 12:00:00 PM CST
*****************************************************/

#include "pisces_response.h"

static void *saved_fd_cast = NULL;

static char *request_pattern[] = {
    /*"Referer: ", not use at this moment */
    "If-Modified-Since",
    "Range: bytes="
};

void *mymmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset)
{
    void *ptr = NULL;
    if ((ptr = mmap(addr, len, prot, flags, fd, offset)) == (void *)-1) {
        pisces_log(P_LOG_ALT, "mmap error: %s", strerror(errno));
        exit(EXIT_FAILURE);
    }
    return ptr;
}

ssize_t writen(int sockfd, void *filebuf, size_t n)
{
    size_t nleft = n, bytes = 1024000;
    ssize_t nwritten;
    char *buf = filebuf;
    struct timespec req;

    req.tv_nsec = 500000000;    /* 0.5s */
    req.tv_sec = 0;

    while (nleft > 0) {
        if ((nwritten = write(sockfd, buf, bytes)) == -1) {
            if (errno == EINTR)
                nwritten = 0;
            else
                return -1;
        }
        if (nwritten != bytes)
            bytes = 1024000;
        if (bytes < 2048000)    /* speed range 1024000~2048000/0.5 s */
            bytes += 10;
        nleft -= nwritten;
        buf += nwritten;
        nanosleep(&req, NULL);
    }
    return n;
}

void sock_writen(int sockfd, void *filebuf, size_t n)
{
    if ((writen(sockfd, filebuf, n) != n) && (errno != ECONNRESET)) {
        if (munmap(filebuf, n) == -1) {
            pisces_log(P_LOG_ERR, "munmap: %s", strerror(errno));
            exit(EXIT_FAILURE);
        }
        pisces_log(P_LOG_ERR, "writen: %s", strerror(errno));
        close(sockfd);
        exit(EXIT_FAILURE);
    }
}

char *parse_url(const char *url)
{
    char *firstline = NULL;     /* such as: "GET /favicon.ico HTTP/1.1" */
    int i, j = 4, len = 0;
    char *res = NULL;

    for (i = 0; url[i + 1] != '\n'; i++) ;
    if (i > PATH_MAX)
        exit(EXIT_FAILURE);
    firstline = (char *)malloc(i);
    strncpy(firstline, url, i);

    len = i - 12;               /* get request file length */
    res = (char *)malloc(i + 1);
    memset(res, 0, i + 1);
    for (i = 0; i < len - 1; i++, j++)
        res[i] = firstline[j];

    /* remove tail '/', makes 'xx.com/' equal to 'xx.com'
     * this can be done by response 301 and with Location tag
     * such as:
     * request: xx.com/foo
     * response: HTTP/1.1 301 Moved Permanetly
     *           Location: http://xxx.com/tmp/
     */
    if (i > 1)
        if (res[i - 1] == '/')
            res[i - 1] = '\0';
    return res;
}

static void parse(char *url)
{                               /* remove '?' */
    int i;
    for (i = 0; url[i + 1] != '\0'; i++) ;
    if (url[i] == '?')
        url[i] = '\0';
}

static void parse_header(char *header, abby_response * resp, const char *path)
{
    char *res = NULL;
    char offset[20], end[20];
    int i, k, n;

    resp->range.offset = -1;
    resp->range.end = -1;
    memset(offset, 0, 20);
    memset(end, 0, 20);

    for (n = 0; n < 2; n++) {
        if ((res = strstr(header, request_pattern[n]))) {
            if (n == 0) {
                char l_mtime[30];
                struct tm *gm = (struct tm *)malloc(sizeof(struct tm));
                strftime(l_mtime, 30, "%a, %d %Y %H:%M:%S GMT", gmtime_r(&resp->mtime, gm));
                free(gm);
                if (strstr(header, l_mtime))
                    resp->code = 304;
            }
            if (n == 1) {
                resp->code = 206;
                for (i = 0; res[i] != '-'; i++) ;
                k = i - 13;
                strncpy(offset, &res[13], k);
                resp->range.offset = atoll(offset);

                i += 1;
                if (res[i + 1] == '\r')
                    resp->range.end = -1;
                else
                    k = i;
                for (; res[i] != '\r'; i++) ;
                strncpy(end, &res[k], i - k);
                resp->range.end = atoll(end);
            }
        }
    }
}

void do_cgi(int sockfd, char *path)
{
    int p[2];
    size_t buf_size = 1024;
    char *buf = NULL;
    int error = 0;
    struct html page;
    abby_response resp;

    buf = (char *)malloc(buf_size);
    pipe(p);

    parse(path);
    if (fork() == 0) {
        close(p[0]);
        dup2(p[1], STDOUT_FILENO);
        if (execl(path, path, NULL) == -1)      /* 简单、粗暴 ORZ */
            error = -1;
    }

    if (error == -1)
        strcpy(buf, "<p style=\"color:red; font-size:60px\">No such file or directory</p>");
    else {
        memset(buf, 0, buf_size);
        read(p[0], buf, buf_size);
        close(p[1]);
    }
    bzero(&page, sizeof(struct html));
    make_page(buf, &page, false);
    resp.code = 200;
    resp.confd = sockfd;
    resp.size = strlen(buf);
    resp.type = "text/html";
    resp.range.offset = -1;
    resp.range.end = -1;
    resp.mtime = 0;
    http_response(resp);
    sock_writen(sockfd, page.html_page, page.size);

    wait(NULL);
    free(buf);
}

void UrlDecode(char *p)
{
    register int i = 0;
    for (; *(p + i);) {
        if ((*p = *(p + i)) == '%') {

            *p = *(p + i + 1) >= 'A' ? ((*(p + i + 1) - 'A') + 10) << 4 : (*(p + i + 1) - '0') << 4;
            *p += *(p + i + 2) >= 'A' ? (*(p + i + 2) - 'A') + 10 : (*(p + i + 2) - '0');
            i += 2;
        }
        p++;
    }
    *p = '\0';
}

extern pisces_ctx_t *ctx;
void transfer_data(int sockfd)
{
    int fd;
    void *fd_cast = NULL;       /* mapped fd */
    struct stat file_stat;
    abby_response resp;
    char filetype[32];
    char *filepath = NULL;
    char *reqpath = NULL;
    char *root_dir = NULL;

    root_dir = ctx->conf->root_dir;

    fd_set r_set;
    struct timeval tv;
    int nfd = 0, res = 0;
    char request[CONTENT_SIZE];
    memset(request, 0, CONTENT_SIZE);
    FD_ZERO(&r_set);
    for (;;) {
        tv.tv_sec = 10;         /* child keep alive timeout 10s */
        tv.tv_usec = 0;
        FD_SET(sockfd, &r_set);
        nfd = sockfd + 1;
        res = select(nfd, &r_set, NULL, NULL, &tv);
        if (res <= 0) {
            if (saved_fd_cast)
                if (munmap(saved_fd_cast, file_stat.st_size) == -1) {
                    pisces_log(P_LOG_ERR, "munmap: %s", strerror(errno));
                    exit(EXIT_FAILURE);
                }
            shutdown(sockfd, SHUT_RDWR);
            exit(1);            /* on error or timeout */
        }
        if (FD_ISSET(sockfd, &r_set)) {
            FD_CLR(sockfd, &r_set);
            int rdres;
            rdres = read(sockfd, request, CONTENT_SIZE);
            if (rdres == -1) {
                close(sockfd);
                pisces_log(P_LOG_ALT, "read request failed! %s", strerror(errno));
                exit(1);
            }

            reqpath = (char *)malloc(PATH_MAX);
            memset(reqpath, 0, PATH_MAX);

            filepath = (char *)malloc(PATH_MAX);
            memset(filepath, 0, PATH_MAX);

            reqpath = parse_url(request);
            UrlDecode(reqpath);
            snprintf(filepath, PATH_MAX, "%s%s", root_dir, reqpath);

            if (strstr(filepath, "cgi-bin")) {
                do_cgi(sockfd, filepath);
                free(filepath);
            } else {
                if (lstat(filepath, &file_stat) == -1) {
                    if (errno == EACCES)
                        pisces_err(sockfd, 403);
                    else
                        pisces_err(sockfd, 404);
                    exit(1);
                }

                if (S_ISDIR(file_stat.st_mode)) {
                    resp.code = 200;
                    parse_header(request, &resp, filepath);

                    char tmp[strlen(filepath) + strlen("/index.html") + 1];
                    snprintf(tmp, sizeof(tmp), "%s/index.html", filepath);
                    if ((fd = open(tmp, O_RDONLY)) == -1) {     /* test if index.html exist */
                        if (errno == EACCES) {  /* request file NOT "favicon" */
                            if (!(strcmp("favicon", filepath) == 0))
                                pisces_err(sockfd, 403);
                            free(reqpath);
                            free(filepath);
                            exit(EXIT_FAILURE);
                        }
                        make_link(sockfd, root_dir, reqpath);
                        free(reqpath);
                        free(filepath);
                    } else {
                        /* send index.html */
                        fstat(fd, &file_stat);
                        resp.mtime = file_stat.st_mtim.tv_sec;  /* convert to GMT in http_response() */
                        char buf[file_stat.st_size];
                        read(fd, buf, file_stat.st_size);
                        resp.confd = sockfd;
                        resp.size = file_stat.st_size;
                        resp.type = "text/html";
                        resp.range.offset = -1;
                        http_response(resp);
                        sock_writen(sockfd, buf, sizeof(buf));
                        free(filepath);
                    }
                } else {
                    resp.code = 200;
                    parse_header(request, &resp, filepath);

                    if (resp.code == 304) {
                        free(filepath);
                        http_response(resp);    /* only response heaher */
                        continue;
                    }

                    fd = open(filepath, O_RDONLY);
                    if (fd == -1) {
                        if (errno == EACCES) {  /* access denied or not found */
                            if (!(strcmp("favicon", filepath) == 0))    /* request file NOT "favicon" */
                                pisces_err(sockfd, 403);
                        }
                        free(filepath);
                        exit(EXIT_FAILURE);
                    }
                    fd_cast = mymmap(0, file_stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
                    saved_fd_cast = fd_cast;
                    close(fd);

                    parse_ext(filetype, filepath);
                    free(filepath);

                    resp.confd = sockfd;
                    resp.type = filetype;
                    resp.size = file_stat.st_size;
                    resp.mtime = file_stat.st_mtim.tv_sec;      /* convert in http_response() */

                    /* 'Range bytes=' not found in header, or found Range bytes=0- ;
                     * this will overwrite 304 */
                    resp.code = resp.range.offset < 1 ? 200 : 206;

                    http_response(resp);
                    if (resp.range.offset > 0)
                        fd_cast += resp.range.offset;
                    sock_writen(sockfd, fd_cast, file_stat.st_size);

                    if (munmap(saved_fd_cast, file_stat.st_size) == -1) {
                        pisces_log(P_LOG_ERR, "munmap: %s", strerror(errno));
                        exit(EXIT_FAILURE);
                    }
                }
            }
        }
    }
}
